<template>
  <div class="clearfix">
    <!-- 上传多个文件 -->
    <template v-if="multiple">
      <a-upload
        :multiple="true"
        :fileList="fileUrl"
        :beforeUpload="handleBeforeUpload"
        :customRequest="handleCustomFileUpload"
        :listType="listType"
        @preview="handlePreview"
        :remove="handleRemove"
      >
        <template v-if="maxnum === -1 || fileUrl.length < maxnum">
          <div v-if="listType === 'picture-card'">
            <a-icon :type="loading ? 'loading' : 'plus'" />
            <div class="ant-upload-text">{{ this.showText }}</div>
          </div>
          <a-button v-else>
            <a-icon :type="loading ? 'loading' : 'plus'" />{{ this.showText }}
          </a-button>
        </template>
      </a-upload>

      <a-modal
        :visible="previewImage !== null"
        @cancel="previewImage = null"
        :footer="null"
      >
        <img
          style="width: 100%"
          v-if="previewImage"
          :src="previewImage"
          alt="example"
        />
      </a-modal>
    </template>

    <!-- 上传单个文件 -->
    <template v-else>
      <a-upload
        :multiple="false"
        v-model="fileUrl"
        :showUploadList="false"
        :beforeUpload="handleBeforeUpload"
        @change="handleUploadChange"
        :action="uploadApiUrl"
        :listType="listType"
      >
        <img
          class="thumb"
          v-if="fileUrl.respUrl && listType === 'picture-card'"
          :src="fileUrl.url"
          alt="avatar"
        />
        <template v-else>
          <div v-if="listType === 'picture-card'">
            <a-icon :type="loading ? 'loading' : 'plus'" />
            <div class="ant-upload-text">{{ this.showText }}</div>
          </div>
          <a-button v-else>
            <a-icon :type="loading ? 'loading' : 'plus'" />{{ this.showText }}
          </a-button>
        </template>
      </a-upload>
      <label v-if="listType !== 'picture-card' && fileUrl.name">
        {{ fileUrl.name }}
      </label>
    </template>
  </div>
</template>

<script>
import axios from "@/axios";
import conf from "@/config";
export default {
  name: "AntdFileUpload",
  props: {
    // 表单校验用字段
    value: [String, Array, Object],
    //是否允许上传多个文件
    multiple: { type: Boolean, default: false },
    //多选时最多上传的文件数量(-1表示不限制)
    maxnum: {
      type: Number,
      default: -1,
      validator: function (value) {
        if (value === -1) return true;
        return value > 0;
      },
    },
    listType: {
      type: String,
      default: "picture-card",
      validator: function (value) {
        return ["picture-card", "picture", "text"].indexOf(value) !== -1;
      },
    },
    //显示文本
    showText: { type: String, default: "Upload" },
    //是否验证上传文件格式
    verifyFile: { type: Boolean, default: true },
    //允许上传的图片宽高(限制上传图片宽高) {width:width,height:height}
    size: {
      type: Object,
      validator: function (value) {
        //空对象
        if (Object.keys(value).length < 1) return false;
        let isnum = true;
        for (const key in value) {
          if (key === "width" || key === "height") {
            isnum = Number.isFinite(value[key]);
            if (!isnum) break;
          }
        }
        return isnum;
      },
    },
    //充许上传的文件类型
    fileTypes: {
      type: Array,
      default() {
        return conf.uploadFileType || [];
      },
      validator: function (value) {
        value = (value || []).filter((item) => item);
        return value.length > 0;
      },
    },
    //允许上传的文件最大大小
    fileMaxSize: {
      type: Number,
      default() {
        return conf.uploadFileMaxSize || 0;
      },
      validator: function (value) {
        return value > 1;
      },
    },
  },
  data() {
    return {
      loading: false,
      fileUrl: this.multiple ? [] : {},
      previewImage: null,
      isHint: null,
    };
  },
  model: { prop: "value", event: "change" },
  watch: {
    //监听父组件传递过来的文件信息
    value: {
      deep: true,
      immediate: false,
      handler: function (newVal, oldVal) {
        if (!newVal) {
          this.fileUrl = this.multiple ? [] : {};
          return false;
        }
        //上传单个文件赋值
        if (!this.multiple) {
          if (typeof newVal !== "string") return false;
          if (this.fileUrl.respUrl === newVal) return false;
          this.fileUrl = {
            uid: new Date().getTime(),
            name: newVal,
            url: `${envConf.imgPreviewUrl}${newVal}`,
            respUrl: newVal,
          };
          return false;
        }
        //上传多个文件
        if (!Array.isArray(newVal)) {
          if (typeof newVal !== "string") return false;
          newVal = newVal.split("|");
        }
        newVal.forEach((item, index) => {
          if (typeof item !== "string") return true;
          const newFileUrl = this.fileUrl.find((x) => x.respUrl === item);
          if (newFileUrl) return true;
          this.fileUrl.push({
            uid: new Date().getTime() + item.length + index,
            name: item,
            url: `${envConf.imgPreviewUrl}${item}`,
            respUrl: item,
          });
        });
      },
    },

    //监听数据变化，及时提交给父组件
    fileUrl: {
      deep: true,
      immediate: false,
      handler: function (newVal, oldVal) {
        if (!newVal) return false;
        if (this.multiple && newVal.length < 1) newVal = null;
        if (!this.multiple && Object.keys(newVal) < 1) newVal = null;
        this.$emit("change", newVal);
      },
    },
  },
  computed: {
    uploadApiUrl: function () {
      if (!envConf.fileUploadUrl) {
        throw new Error("请先配置文件上传接口地址");
      }
      return envConf.fileUploadUrl;
    },
    isVerify: function () {
      const types = (this.fileTypes || []).filter((item) => item);
      if (this.verifyFile && types.length < 1 && this.fileMaxSize < 1)
        return false;
      return true;
    },
  },
  methods: {
    //文件上传前事件
    handleBeforeUpload(file, fileList) {
      return new Promise(async (resolve, reject) => {
        try {
          //验证上传文件
          const err = await this.handleVerifyFile(file);
          if (err) throw new Error(err);
          return resolve(true);
        } catch (error) {
          this.$message.error(error + "");
          return reject(false);
        }
      });
    },
    //验证上传文件
    async handleVerifyFile(file) {
      // 不需要验证上传的文件
      if (!this.isVerify) return null;
      // 验证上传文件大小
      if (this.fileMaxSize > 0 && file.size > this.fileMaxSize) {
        return `请上传${this.fileMaxSize / 1024 / 1024}MB大小以内的文件`;
      }
      // 验证上传文件类型
      if (this.fileTypes.length > 0 && !this.fileTypes.includes(file.type)) {
        return "暂不支持该类型文件";
      }
      //验证图片宽高
      return await this.checkImageWH(file);
    },
    //验证图片宽高
    checkImageWH(file) {
      return new Promise((resolve, reject) => {
        if (file.type.indexOf("image") === -1) {
          return resolve(null);
        }
        const _self = this;
        const filereader = new FileReader();
        filereader.onload = (e) => {
          try {
            const image = new Image();
            image.onload = function () {
              if (!_self.size) return resolve(null);
              if (_self.size.width && this.width > _self.size.width) {
                return reject(`请上传宽为${_self.size.width}的图片`);
              }
              if (_self.size.height && this.height > _self.size.height) {
                return reject(`请上传高为${_self.size.height}的图片`);
              }
              return resolve(null);
            };
            image.onerror = reject;
            image.src = e.target.result;
          } catch (error) {
            return reject(error);
          }
        };
        filereader.readAsDataURL(file);
      });
    },
    //自定义文件上传事件
    handleCustomFileUpload(info) {
      //调用父类改变事件
      this.$emit("uploadChange", { file: info.file, fileList: this.fileUrl });

      //判断是否超过充许上传的文件数量
      if (this.maxnum > 0 && this.fileUrl.length >= this.maxnum) {
        if (!this.isHint) {
          this.$message.error(`最多充许上传${this.maxnum}个文件`);
          this.isHint = true;
        }
        return false;
      }
      const thisFile = {
        uid: info.file.uid,
        name: info.file.name,
        status: "uploading",
        type: info.file.type,
        size: info.file.size,
      };
      this.showUploadMultipleFile(thisFile);
      const formData = new FormData();
      formData.append(info.filename, info.file);
      axios
        .request({
          url: this.uploadApiUrl,
          method: "POST",
          processData: false,
          data: formData,
        })
        .then((res) => {
          thisFile.status = "error";
          if (res.serveStatus === 200 && res.status === 1) {
            thisFile.status = "done";
            thisFile.respUrl = res.data;
            thisFile.url = URL.createObjectURL(info.file);
          }
          this.showUploadMultipleFile(thisFile);
        })
        .catch((e) => {
          thisFile.status = "error";
          this.showUploadMultipleFile(thisFile);
        });
    },
    //显示上传多个文件
    showUploadMultipleFile(file) {
      if (file.status === "error") {
        if (file.url) file.url = undefined;
        this.$message.error(`${file.name}上传失败`);
      }
      const fileList = this.fileUrl || [];
      const existFile = fileList.find((x) => x.uid === file.uid);
      if (!existFile) {
        fileList.push(file);
      } else {
        const index = fileList.findIndex((x) => x.uid === file.uid);
        if (file.status === "removed") {
          fileList.splice(index, 1);
        } else {
          existFile.status = file.status;
          fileList.splice(index, 1, existFile);
        }
      }
      this.fileUrl = fileList;
    },
    handlePreview(file) {
      this.previewImage = file.url;
    },
    //移除已上传文件
    handleRemove(file) {
      file.status = "removed";
      this.showUploadMultipleFile(file);
    },
    //文件上传改变事件
    handleUploadChange(info) {
      //调用父类改变事件
      this.$emit("uploadChange", info);
      const resp = info.file.response || {};
      //文件上传错误
      if (info.file.status === "error") {
        let hint = `${info.file.name}上传失败`;
        if (resp.message) hint += `, ${resp.message}`;
        this.$message.error(hint);
        if (this.loading) this.loading = false;
        return false;
      }
      if (info.file.status === "uploading") {
        this.loading = true;
      }
      if (info.file.status === "done") {
        if (resp.serveStatus === 200 && resp.status === 1) {
          this.fileUrl = resp.data;
        }
        this.loading = false;
      }
    },
  },
};
</script>

<style lang="less" scoped>
.clearfix {
  display: -webkit-flex;
  display: flex;
  flex: auto;
}

/deep/ .ant-modal-close-x {
  color: red;
  line-height: 26px;
  width: 35px;
  height: initial;
}

/deep/ .ant-upload.ant-upload-select-picture-card {
  margin-bottom: 0;
}

/deep/ .ant-form-item-control {
  line-height: 0;
}
</style>